<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>compose 组合</title>
</head>

<body>

</body>
<script>
   function add1(str){
     return str + '1'
   }
   function add2(str){
     return str + '2'
   }
   function add3(str1){
     return str1 + '3' 
   }
   // 这种函数执行有序，内部函数的返回值是外部函数的输入结果，如果数量一多，极其难以区分
   console.log(add3(add2(add1('1'))))
   
   function compose(funArr){
     // reduce 如果不传第二个参数值只执行数组length -1 次。比如[1,2,3].reduce 不传初始值，回调只执行2次
     // 不传初始值，直接空数组reduce 会报错
     if(funArr.length == 0 | funArr) return;   
     return (...str) =>{
        if(funArr.length == 1) return funArr[0](...str);
        return funArr.reduce(function(p,v,index,arr){
          if(index==1) p = p(...str)  // 第一次执行回调要特殊，因为外界传值
          return p = v(p) 
          })
        }
   }
   var funArr = [add1,add2,add3];
   // 入口a1 只需要一个参数，我多传这样写兼容不报错，因为js 中函数形参和实参本来本来不一致就不不会报错，兼容性更好
   var result = compose([add1,add2,add3])('1','kkk','ppp');
   console.log(result);
   // 改良上面的compose 写法，不定参数的形式，同时将执行顺序改成从右向左
   function compose2(...funArr){
      var args = arguments; 
      if(!args.length) return;
      var func = (...last) =>{
        if(funArr.length == 1) return funArr[0](...last); 
        // currentIndex 是v 的下摆哦，那么从有右往左，第一次 p 是数组最后一位，v 就是倒是第二位，对应下标为arr.length - 2
        return funArr.reduceRight(function(p,v,currentIndex,arr){
            // 第一次执行回调要特殊，因为外界要传值,这个逻辑判断太麻烦了
            if(currentIndex == arr.length - 2) p = p(...last)  
            return p = v(p) 
          })
      }
      return func;
   }
   //  执行顺序3，2，1， 也就是add3 的结果作为add2 的输入条件，依次类推，直到完成
   result = compose2(add1,add2,add3)('1');
   console.log(result);
   
    // 继续优化，最终版本
   function compose3(...funArr){
      var args = arguments; 
      if(!args.length) return;
      var func = (...last) =>{
        // if(funArr.length == 1) return funArr[0](...last); 
        // 使用初始值这时候第一次就能确保p 是输入的参数，而不是函数,并且回调函数至少执行一次
        // 使用了初始值，不管有没有传参，p 都是一个数组，没传参 p 是一个[]
        // 如果所有函数的返回值统一返回一个数组，就可以去掉判断语句，更加精炼
        return funArr.reduceRight(function(p,v){ 
              //  if(Array.isArray(p))  return p = v(...p);
               return p = v(...p)
          },last)
      }
      return func;
   }
  //  result = compose3(add1,add2,add3)('1','2');
  //  console.log(result);

   // 未传入的形参默认值是undefined,js 是不报错的，对于函数流程，设计的时候要注意前一个flow 的返回值
   // 得是下一个flow 的输入，所以必须22 对应
   // 设计上解决：另一方面一个流程启动，我们只关心最初的输入条件，不管中间的环节，所以中间函数的参数仅仅至于它上一个流程的
   // 返回值有关，可以统一设置为补丁参数，也就是我的各个add 都返回一个数组即可

   /*   解决方法，柯里化参数缓存，全部变成单参数函数；
     或者套一层函数，设置统一的参数格式和默认值；
     使用函数式编程函子模式 */
  
   // 这么规范flow 的返回值的话，我们就不用再reduce 函数内部判断p 究竟是数组还是一个值了
   function a1(str1,str2,str3){
     return [str1+ 'a1' + str2 + 'a1' + str3]
   }
   function a2(str){
     return [str + 'a2',str + 'a2',str + 'a2']
   }
   function a3(str1,str2){
     return [str1 + 'a3' + str2]
   }
  //执行顺序a3 --- > a2 --- a1-->
  // 最初输入值 ‘1’ 和 ‘2’
  //  result = compose3(a1,a2,a3)('1','2');
  //  console.log(result);
  //执行顺序 a2 --- a1--> a3
  // 最初输入值 ‘1’ 
  result = compose3(a3,a1,a2)();
  console.log(result);
</script>

</html>